#include "mc_ssta.h"

void mc_ssta::gen_mc_delay(class ff_graph& ff_obj)
{
    //default_random_engine normal_generator;
    normal_distribution<float> distribution(0, 1);

    // the Mersenne Twister with a popular choice of parameters
    typedef std::mt19937 MyRNG;  
    MyRNG rng;                   // e.g. keep one global instance (per thread)

    // generate deterministic delay for each simulation
    mc_vertex_delay.assign(ff_obj.get_vertex_num(), 0);

    for (unsigned int i = 0; i < ff_obj.get_vertex_num(); i++) 
    {
        /*
        * Implementation note:
        *  We should simulate all falling and rising delays
        *  Current design is for simplicity
        */

        //unsigned int delay_num = ff_obj.vertex_fanin.size() * 2;
        unsigned int delay_num = 1;

        vector <float> delays; //store all delays for single gate 
        delays.assign(delay_num, 0);

        rng.seed(rand_seed);

        for (unsigned int k = 0; k < delay_num; k++)
        {
            delays[k] = ff_obj.vertex_delay[i][0];
            for (unsigned int j = 1; j < VARNUM; j++) 
            {
                float rand = distribution(rng);
                delays[k] += ff_obj.vertex_delay[i][j] * rand;
            }
        }
        rand_seed = rand_seed + 1;

        float delay = *max_element(delays.begin(),delays.end());

        if (delay < 0) {
            delay = 0.0; //in case generate negative delay
        }

        mc_vertex_delay[i] = delay;
    }

    for (unsigned int i = 0; i < ff_obj.get_vertex_num(); i++) 
    {
        //assign gate's weight to fanout edge 
        vector<edge>::iterator edge_it;
        for (edge_it = ff_obj.vertex_fanout[i].begin(); 
            edge_it != ff_obj.vertex_fanout[i].end(); ++edge_it)
        {
            (*edge_it).delay = mc_vertex_delay[i];
        }

        for (edge_it = ff_obj.vertex_fanin[i].begin(); 
            edge_it != ff_obj.vertex_fanin[i].end(); ++edge_it)
        {
            (*edge_it).delay = mc_vertex_delay[(*edge_it).in];
        }
    }
}

float mc_ssta::sta_pert(class ff_graph& ff_obj)
{
    //intial arrive time 
    vertex_arrive_time.assign(ff_obj.get_vertex_num(), 0);

    assert(ff_obj.tp_order.size() == ff_obj.get_vertex_num());
    
    vector<unsigned int>::iterator tp_it;
    for (tp_it = ff_obj.tp_order.begin(); 
            tp_it != ff_obj.tp_order.end(); tp_it++)
    {
        unsigned int vertex = *tp_it;

        vector<edge>::iterator edge_it;
        for (edge_it = ff_obj.vertex_fanin[vertex].begin(); 
            edge_it != ff_obj.vertex_fanin[vertex].end(); ++edge_it)
        {
            unsigned int source = (*edge_it).in;
            //accumulate path delay
            float temp_delay = (*edge_it).delay + vertex_arrive_time[source];

            //update arrive time
            vertex_arrive_time[vertex] = 
                (vertex_arrive_time[vertex] > temp_delay) 
                ? vertex_arrive_time[vertex] : temp_delay;
        }
    }

    // max_path_delay;
    return vertex_arrive_time[ff_obj.get_vertex_num() - 1];
}

float mc_ssta::mc_ssta_pert(class ff_graph& ff_obj, 
                        unsigned int mc_num, float clock)
{
    unsigned int m = 0; //store successful timing verification times
    rand_seed = 100;

    vector <float> max_path_delays;
    float mean = 0.0;
    float var = 0.0;

    for (unsigned int n = 0; n < mc_num; n++) 
    {
        gen_mc_delay(ff_obj);

        //compute max_path_delay
        float max_path_delay = sta_pert(ff_obj);
        max_path_delays.push_back(max_path_delay);
        mean = max_path_delay + mean;

        //timing verification
        if (clock > max_path_delay)
        {
            m++;
        }
    }

    mean = mean/(float)mc_num; 
    for (unsigned int n = 0; n < mc_num; n++) 
    {
        var = var + (max_path_delays[n] - mean) * (max_path_delays[n] - mean);
    }
    var = var/(float)mc_num;

    //compute timing yield
    float yield = (float) m/mc_num;
    return yield;
}

